/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    json_document.cpp  
  Description: 

*/

#include "stdafx.h"


using namespace m;
using namespace m::alloc;
using namespace m::error;

#define OBJECT_START L"{"
#define OBJECT_END L"}"
#define ARRAY_START L"["
#define ARRAY_END L"]"
#define STRING_DELIM L"\""
#define PAIR_SEPARATOR L":"
#define MEMBER_SEPARATOR L","
#define SPACE L" "
#define NEWLINE L"\r\n"

#define TOK_NIL L"nil"
#define TOK_TRUE L"true"
#define TOK_FALSE L"false"

enum Token
{
	ObjectStart,
	ObjectEnd,
	ArrayStart,
	ArrayEnd,
	StringDelim,
	PairSeparator,
	MemberSeparator,
	StringValue,
	NumberValue,
	TrueValue,
	FalseValue,
	NilValue
};

#define SPACES_PER_INDENT 3

#define ARRAY_ITEMS_PER_LINE 16

namespace m
{
	namespace text
	{
		json_document::json_document()
			: _Root(nullptr),
			_Buffer(nullptr)
		{
		}

		json_document::~json_document()
		{
			clear(_Root);
			clear(_Buffer);
		}

		void json_document::load_document(text_buffer * Reader)
		{
			assert(Reader);
			if (Reader)
			{
				clear(_Root);
				ResetParser(Reader);
				_Root = ReadValue(nullptr);
			}
		}

		void json_document::save_document(text_buffer * Writer)
		{
			assert(Writer);
			if (Writer)
			{
				ResetParser(Writer);
				_Buffer->set_size(0);

				if (_Root)
				{
					switch (_Root->type())
					{
					case JsonArray:
						WriteArray(_Root);
						break;
					case JsonObject:
						WriteObject(_Root);
						break;
					case JsonBool:
						WriteBool(_Root);
						break;
					case JsonNumber:
						WriteNumber(_Root);
						break;
					case JsonString:
						WriteString(_Root);
						break;
					case JsonNil:
						WriteNil(_Root);
						break;
					}
				}
			}
		}

		json_node *json_document::root()
		{
			return _Root;
		}

		const json_node *json_document::root() const
		{
			return _Root;
		}

		void json_document::set_root(json_node * Root)
		{
			assign(_Root, Root);
		}

		void json_document::WriteValue(json_node* cur)
		{
			switch (cur->type())
			{
			case JsonArray:
				WriteArray(cur);
				break;
			case JsonObject:
				WriteObject(cur);
				break;
			case JsonBool:
				WriteBool(cur);
				break;
			case JsonNumber:
				WriteNumber(cur);
				break;
			case JsonString:
				WriteString(cur);
				break;
			case JsonNil:
				WriteNil(cur);
				break;
			}
		}

		void json_document::WriteObject(json_node* cur)
		{
			_Buffer->write(OBJECT_START NEWLINE);

			json_node* child = cur->child();
			while (child)
			{
				bool indented = false;
				if (_Indent < CHAR_MAX)
				{
					indented = true;
					++_Indent;
				}

				WriteWhitespace();

				ref_storage track1;
				json_node* name = create(track1, new json_node());
				name->set_value(child->name());
				WriteString(name);
				_Buffer->write(SPACE PAIR_SEPARATOR SPACE);

				WriteValue(child);

				if (indented)
				{
					--_Indent;
				}

				child = child->_Sibling;
				if (child)
				{
					_Buffer->write(MEMBER_SEPARATOR);
				}
				_Buffer->write(NEWLINE);
			}

			WriteWhitespace();
			_Buffer->write(OBJECT_END);
		}

		void json_document::WriteArray(json_node* cur)
		{
			size_t idx = 0;

			_Buffer->write(ARRAY_START NEWLINE);

			json_node* child = cur->child();
			while (child)
			{
				bool indented = false;
				if (_Indent < CHAR_MAX)
				{
					indented = true;
					++_Indent;
				}

				if (idx == 0 || (idx > 1 && ((idx - 1) % ARRAY_ITEMS_PER_LINE) == 0))
				{
					WriteWhitespace();
				}

				WriteValue(child);

				if (indented)
				{
					--_Indent;
				}

				child = child->_Sibling;
				if (child)
				{
					_Buffer->write(MEMBER_SEPARATOR);
				}
				if (child == nullptr || (idx > 0 && (idx % ARRAY_ITEMS_PER_LINE) == 0))
				{
					_Buffer->write(NEWLINE);
				}
				else
				{
					_Buffer->write(SPACE);
				}
				++idx;
			}

			WriteWhitespace();
			_Buffer->write(ARRAY_END);
		}

		void json_document::WriteNumber(json_node* cur)
		{
			wchar_t Buf[DBL_MAX_10_EXP + 1] = { 0 };
			StringCchPrintf(Buf, ARRAYSIZE(Buf), L"%lg", cur->as_number());
			_Buffer->write(Buf);
		}

		void json_document::WriteBool(json_node* cur)
		{
			if (cur->as_bool())
			{
				_Buffer->write(TOK_TRUE);
			}
			else
			{
				_Buffer->write(TOK_FALSE);
			}
		}

		void json_document::WriteString(json_node* cur)
		{
			_Buffer->write(STRING_DELIM);

			const wchar_t* ch = cur->as_string();
			while (*ch != 0)
			{
				if (*ch > CHAR_MAX)
				{
					wchar_t hex[7] = { 0 };
					wsprintf(hex, L"\\u%0x", *ch);
					_Buffer->write(hex);
				}
				else
				{
					switch (*ch)
					{
					case L'\"':
						_Buffer->write(L"\\\"");
						break;
					case L'\\':
						_Buffer->write(L"\\\\");
						break;
					case L'/':
						_Buffer->write(L"\\/");
						break;
					case L'\b':
						_Buffer->write(L"\\b");
						break;
					case L'\f':
						_Buffer->write(L"\\f");
						break;
					case L'\n':
						_Buffer->write(L"\\n");
						break;
					case L'\r':
						_Buffer->write(L"\\r");
						break;
					case L'\t':
						_Buffer->write(L"\\t");
						break;
					default:
						if (!iswcntrl(*ch))
						{
							_Buffer->write(*ch);
						}
						break;
					}
				}

				++ch;
			}

			_Buffer->write(STRING_DELIM);
		}

		void json_document::WriteNil(json_node* cur)
		{
			_Buffer->write(TOK_NIL);
		}

		void json_document::WriteWhitespace()
		{
			for (int i = 0; i < _Indent * SPACES_PER_INDENT; ++i)
			{
				_Buffer->write(SPACE);
			}
		}

		json_node* json_document::ReadValue(LPCWSTR endToken)
		{
			ref_storage track1;
			json_node* ret = nullptr;
			wchar_t ch;
			ch = ReadToken();
			if (ch)
			{
				switch (ch)
				{
					case *OBJECT_START:
					{
						ret = create(track1, ReadObject());
						break;
					}
					case *ARRAY_START:
					{
						ret = create(track1, ReadArray());
						break;
					}
					case *STRING_DELIM:
					{
						ret = create(track1, ReadString());
						break;
					}
					case 'n':
					{
						if (PopChar() == 'i')
						{
							if (PopChar() == 'l')
							{
								ret = create(track1, new json_node());
								ret->set_nil();
							}
						}
					}
					break;
					case 't':
					{
						if (PopChar() == 'r')
						{
							if (PopChar() == 'u')
							{
								if (PopChar() == 'e')
								{
									ret = create(track1, new json_node());
									ret->set_value(true);
								}
							}
						}
					}
					break;
					case 'f':
					{
						if (PopChar() == 'a')
						{
							if (PopChar() == 'l')
							{
								if (PopChar() == 's')
								{
									if (PopChar() == 'e')
									{
										ret = create(track1, new json_node());
										ret->set_value(false);
									}
								}
							}
						}
					}
					break;
					case '-':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
					{
						wchar_t str[DBL_MAX_10_EXP + 1] = { 0 };
						size_t idx = 0;

						do
						{
							str[idx] = ch;
							++idx;

							ch = PopChar();
							if (ch != 'e' && ch != 'E' && ch != '+' && ch != '-' && ch != '.' && !(ch >= '0' && ch <= '9'))
							{
								break;
							}
						} while (idx < DBL_MAX_10_EXP);

						double num;
						if (swscanf_s(str, L"%lg", &num) == 1)
						{
							ret = create(track1, new json_node());
							ret->set_value(num);
						}
					}
					default:
					{
						if (endToken)
						{
							for (size_t i = 0; i < wcslen(endToken); ++i)
							{
								if (ch == endToken[i])
								{
									return value(track1, ret);
								}
							}
							if (ret && ret->type() == JsonNumber)
							{
								throw json_format_exception(_Line, _Col, "Too many values.");
							}
						}
					}
				}
			}

			return value(track1, ret);
		}

		wchar_t json_document::ReadToken()
		{
			wchar_t ch;

			do
			{
				ch = PopChar();
			}
			while (iswspace(ch) > 0);

			return ch;
		}

		json_node* json_document::ReadObject()
		{
			ref_storage track1;
			json_node* ret = create(track1, new json_node());
			json_node* last = nullptr;
			ret->set_object();

			wchar_t tok = 0;

			for (;;)
			{
				ref_storage track2;
				json_node* name = create(track2, ReadValue(SPACE NEWLINE OBJECT_END PAIR_SEPARATOR));
				if (!name)
				{
					break;
				}

				if (name->type() != JsonString)
				{
					throw json_format_exception(_Line, _Col, "Invalid parameter.");
				}

				tok = ReadToken();
				if (tok != *PAIR_SEPARATOR)
				{
					throw json_format_exception(_Line, _Col, "Missing separator.");
				}

				ref_storage track3;
				json_node* value = create(track3, ReadValue(SPACE NEWLINE OBJECT_END MEMBER_SEPARATOR));
				if (!value)
				{
					throw json_format_exception(_Line, _Col, "Missing value.");
				}
				value->set_name(name->as_string());
				if (last)
				{
					value->insert_after(last);
				}
				else
				{
					ret->insert_child(value);
				}
				last = value;
				if (value->type() != JsonNumber)
				{
					tok = ReadToken();
					if (tok == *OBJECT_END)
					{
						break;
					}
					else if (tok != *MEMBER_SEPARATOR)
					{
						throw json_format_exception(_Line, _Col, "Too many values.");
					}
				}
			}

			return value(track1, ret);
		}

		json_node* json_document::ReadArray()
		{
			ref_storage track1;
			json_node* ret = create(track1, new json_node());
			json_node* last = nullptr;
			ret->set_array();

			wchar_t tok = 0;

			for (;;)
			{
				ref_storage track2;
				json_node* value = create(track2, ReadValue(SPACE NEWLINE ARRAY_END MEMBER_SEPARATOR));
				if (!value)
				{
					break;
				}

				if (last)
				{
					value->insert_after(last);
				}
				else
				{
					ret->insert_child(value);
				}

				last = value;

				if (value->type() != JsonNumber)
				{
					tok = ReadToken();
					if (tok == *ARRAY_END)
					{
						break;
					}
					else if (tok != *MEMBER_SEPARATOR)
					{
						throw json_format_exception(_Line, _Col, "Too many values.");
					}
				}
			}

			return value(track1, ret);
		}

		json_node* json_document::ReadString()
		{
			ref_storage track1;
			text_buffer* buf = create(track1, new text_buffer());

			bool escape = false;
			wchar_t ch;
			for (;;)
			{
				ch = PopChar();
				if (!ch)
				{
					throw json_format_exception(_Line, _Col, "String missing terminator.");
				}

				if (escape)
				{
					escape = false;
					switch (ch)
					{
					case L'"':
						_Buffer->write('"');
						break;
					case L'\\':
						_Buffer->write('\\');
						break;
					case L'/':
						_Buffer->write('/');
						break;
					case L'b':
						_Buffer->write('"');
						break;
					case L'f':
						_Buffer->write(L'\f');
						break;
					case L'n':
						_Buffer->write(L'\n');
						break;
					case L'r':
						_Buffer->write(L'\r');
						break;
					case L't':
						_Buffer->write(L'\t');
						break;
					case 'u':
					{
						ch = 0;

						for (size_t i = 0; i < 4; ++i)
						{
							ch = PopChar();
							if (!ch)
							{
								throw json_format_exception(_Line, _Col, "Incomplete escape sequence.");
							}

							switch (ch)
							{
							case '0':
								ch |= 0;
								break;
							case '1':
								ch |= 1;
								break;
							case '2':
								ch |= 2;
								break;
							case '3':
								ch |= 3;
								break;
							case '4':
								ch |= 4;
								break;
							case '5':
								ch |= 5;
								break;
							case '6':
								ch |= 6;
								break;
							case '7':
								ch |= 7;
								break;
							case '8':
								ch |= 8;
								break;
							case '9':
								ch |= 9;
								break;
							case 'a':
							case 'A':
								ch |= 0xa;
								break;
							case 'b':
							case 'B':
								ch |= 0xb;
								break;
							case 'c':
							case 'C':
								ch |= 0xc;
								break;
							case 'd':
							case 'D':
								ch |= 0xd;
								break;
							case 'e':
							case 'E':
								ch |= 0xe;
								break;
							case 'f':
							case 'F':
								ch |= 0xf;
								break;
							default:
								throw json_format_exception(_Line, _Col, "Invalid character in escape sequence.");
							}

							ch <<= 4;
						}

						_Buffer->write(ch);
					}
					break;
					default:
						throw json_format_exception(_Line, _Col, "Unrecognized escape sequence.");
					}
				}
				else
				{
					if (ch == '\\')
					{
						escape = true;
					}
					else
					{
						if (ch == *STRING_DELIM)
						{
							break;
						}

						if (!iswcntrl(ch))
						{
							buf->write(ch);
						}
					}
				}
			}

			ref_storage track2;
			json_node* ret = create(track2, new json_node());
			wchar_t* str = new wchar_t[buf->size() + 1];
			buf->set_offset(0);
			buf->read(str, buf->size() + 1);
			ret->set_value(str);
			delete[] str;
			return value(track2, ret);
		}

		void m::text::json_document::ResetParser(text_buffer * Buffer)
		{
			assign(_Buffer, Buffer);
			_Buffer->set_offset(0);
			_Line = _Col = 1;
			_Indent = 0;
		}

		wchar_t json_document::PopChar()
		{
			if (_Buffer)
			{
				wchar_t top = _Buffer->read();
				if (top)
				{
					if (top == '\n')
					{
						++_Line;
						_Col = 1;
					}
					else
					{
						++_Col;
					}
					return top;
				}
			}
			return 0;
		}
	}
}